package com.pro.dl.dao.impl;

import java.io.Serializable;
import java.util.List;
import java.util.Map;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Criterion;
import org.hibernate.exception.ConstraintViolationException;

import com.pro.dl.exception.AlreadyPresentObject;
import com.pro.dl.exception.StorageException;
import com.pro.dl.hibernate.HibernateUtils;

public class GenericDaoImpl {

	static Logger logger = LogManager.getLogger(GenericDaoImpl.class);

	protected static SessionFactory sessionFactory;

	public GenericDaoImpl() {
		sessionFactory = HibernateUtils.getSessionFactory();
	}

	/**
	 * 
	 * @param serializableId
	 * @return
	 * @throws StorageException
	 */
	protected <T> T getObjectById(Serializable serializableId, Class<T> result)
			throws StorageException {
		Session session = sessionFactory.getCurrentSession();
		try {
			return (T)session.get(result, serializableId);
		} catch (Exception e) {
			throw new StorageException(String.format(
					"Exception in getting instance of class {} with id {}",
					this.getClass().getTypeParameters()[0], serializableId), e);
		}
	}

	/**
	 * Searches by using named query.
	 * 
	 * @param queryName
	 * @param parameters
	 * @return
	 * @throws StorageException
	 */
	protected <T> List<T> searchObjectsByNamedQuery(String queryName,
			Map<String, Object> parameters) throws StorageException {
		Session session = sessionFactory.getCurrentSession();
		try {
			Query query = session.getNamedQuery(queryName);
			if (parameters != null)
				for (Map.Entry<String, Object> entry : parameters.entrySet()) {
					query.setParameter(entry.getKey(), entry.getValue());
				}
			return (List<T>) query.list();
		} catch (Exception e) {
			throw new StorageException(String.format(
					"Exception in search named query {} with parameters {}",
					queryName, parameters), e);
		} 
	}

	/**
	 * Searches by using named query.
	 * 
	 * @param queryName
	 * @param parameters
	 * @return
	 * @throws StorageException
	 */
	protected <T> T searchUniqueObjectByNamedQuery(String queryName,
			Map<String, Object> parameters) throws StorageException {
		Session session = sessionFactory.getCurrentSession();
		try {
			Query query = session.getNamedQuery(queryName);
			if (parameters != null)
				for (Map.Entry<String, Object> entry : parameters.entrySet()) {
					query.setParameter(entry.getKey(), entry.getValue());
				}
			return (T) query.uniqueResult();
		} catch (Exception e) {
			throw new StorageException(String.format(
					"Exception in search named query {} with parameters {}",
					queryName, parameters), e);

		}
	}

	/**
	 * 
	 * @param criteriaList
	 * @return
	 * @throws StorageException
	 */
	protected <T> T searchUniqueObjectByCriteria(List<Criterion> criteriaList, Class<T> classObj)
			throws StorageException {
		Session session = sessionFactory.getCurrentSession();
		try {
			Criteria cr = session.createCriteria(classObj);
			for (Criterion criterion : criteriaList) {
				cr.add(criterion);
			}
			return (T) cr.uniqueResult();
		} catch (Exception e) {
			throw new StorageException(String.format(
					"Exception in searching with parameters {}", criteriaList),
					e);

		}
	}

	/**
	 * 
	 * @param criteriaList
	 * @return
	 * @throws StorageException
	 */
	protected <T> List<T> searchObjectsByCriteria(List<Criterion> criteriaList, Class<T> classObj)
			throws StorageException {
		Session session = sessionFactory.getCurrentSession();
		try {
			Criteria cr = session.createCriteria(classObj);
			for (Criterion criterion : criteriaList) {
				cr.add(criterion);
			}
			return (List<T>) cr.uniqueResult();
		} catch (Exception e) {
			throw new StorageException(String.format(
					"Exception in searching with parameters {}", criteriaList),
					e);

		} 
	}

	/**
	 * 
	 * @param object
	 * @throws StorageException
	 * @throws AlreadyPresentObject 
	 */
	protected <T> void createObject(T object) throws StorageException, AlreadyPresentObject {
		Session session = sessionFactory.getCurrentSession();
		try {
			session.persist(object);
		} catch (ConstraintViolationException e){
			throw new AlreadyPresentObject(e);
		} catch (Exception e) {
			throw new StorageException(String.format(
					"Exception in creating instance of class %s with %s", getParameterClass(), object), e);
		} 
	}

	private String getParameterClass(){
		return this.getClass().getName();
	}
	
	
	/**
	 * 
	 * @param object
	 * @throws StorageException
	 */
	protected <T> void updateObject(T object) throws StorageException {
		Session session = sessionFactory.getCurrentSession();
		try {
			session.update(object);
		} catch (Exception e) {
			throw new StorageException(String.format(
					"Exception in saving instance of class {} with {}", this
							.getClass().getTypeParameters()[0], object), e);

		} 
	}
	
	
	/**
	 * 
	 * @param object
	 * @throws StorageException
	 */
	protected <T> void updateObject(List<T> objectList) throws StorageException {
		Session session = sessionFactory.getCurrentSession();
		try {
			for(T object : objectList){
				session.save(object);
			}
		} catch (Exception e) {
			throw new StorageException(String.format(
					"Exception in saving instance of class {} with {}", this
							.getClass().getTypeParameters()[0], objectList), e);

		} 
	}

	/**
	 * 
	 * @param queryName
	 * @param parameters
	 * @throws StorageException
	 */
	protected int updateObjectByNamedQuery(String queryName,
			Map<String, Object> parameters) throws StorageException {
		Session session = sessionFactory.getCurrentSession();
		try {
			Query query = session.getNamedQuery(queryName);
			if (parameters != null)
				for (Map.Entry<String, Object> entry : parameters.entrySet()) {
					query.setParameter(entry.getKey(), entry.getValue());
				}
			int updatedCount  = query.executeUpdate();
			session.flush();
			return updatedCount;
		} catch (Exception e) {
			throw new StorageException(String.format(
					"Exception in updating named query {} with parameters {} ",
					queryName, parameters), e);
		}
	}
	
	
	public static void startConversation(){
		Session session = sessionFactory.getCurrentSession();
		session.beginTransaction();
	}
	
	public static void commitConversation(){
		Session session = sessionFactory.getCurrentSession();
		session.getTransaction().commit();
		if(session.isOpen()){
			session.close();
		}
	}

}
